
package com.bpzj.util.messageloop;

import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;

/**
 * Low-level class holding the list of messages to be dispatched by a {@link Looper}.  Messages are not added directly to a MessageQueue, but rather through
 * {@link Handler} objects associated with the Looper.
 *
 * <p>You can retrieve the MessageQueue for the current thread with
 * {@link Looper#myQueue() Looper.myQueue()}.
 */
@Slf4j
public class MessageQueue {

  // True if the message queue can be quit.
  private final boolean mQuitAllowed;

  @SuppressWarnings("unused")
  private int mPtr; // used by native code

  Message mMessages;
  private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
  private IdleHandler[] mPendingIdleHandlers;
  private boolean mQuiting;

  // Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
  private boolean mBlocked;

  // The next barrier token.
  // Barriers are indicated by messages with a null target whose arg1 field carries the token.
  private int mNextBarrierToken;

  // private native void nativeInit();
  // private native void nativeDestroy();
  // private native void nativePollOnce(int ptr, int timeoutMillis);
  // private native void nativeWake(int ptr);

  /**
   * Callback interface for discovering when a thread is going to block waiting for more messages.
   */
  public static interface IdleHandler {

    /**
     * Called when the message queue has run out of messages and will now wait for more.  Return true to keep your idle handler active, false to have it
     * removed.  This may be called if there are still messages pending in the queue, but they are all scheduled to be dispatched after the current time.
     */
    boolean queueIdle();
  }

  /**
   * Add a new {@link IdleHandler} to this message queue.  This may be removed automatically for you by returning false from {@link IdleHandler#queueIdle
   * IdleHandler.queueIdle()} when it is invoked, or explicitly removing it with {@link #removeIdleHandler}.
   *
   * <p>This method is safe to call from any thread.
   *
   * @param handler The IdleHandler to be added.
   */
  public final void addIdleHandler(IdleHandler handler) {
    if (handler == null) {
      throw new NullPointerException("Can't add a null IdleHandler");
    }
    synchronized (this) {
      mIdleHandlers.add(handler);
    }
  }

  /**
   * Remove an {@link IdleHandler} from the queue that was previously added with {@link #addIdleHandler}.  If the given object is not currently in the idle
   * list, nothing is done.
   *
   * @param handler The IdleHandler to be removed.
   */
  public final void removeIdleHandler(IdleHandler handler) {
    synchronized (this) {
      mIdleHandlers.remove(handler);
    }
  }

  MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    // nativeInit();
  }

  @Override
  protected void finalize() throws Throwable {
    try {
      // nativeDestroy();
    } finally {
      super.finalize();
    }
  }

  final Message next() {
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;

    for (; ; ) {
      if (nextPollTimeoutMillis != 0) {
        // Binder.flushPendingCommands();
      }
      // nativePollOnce(mPtr, nextPollTimeoutMillis);

      synchronized (this) {
        if (mQuiting) {
          return null;
        }

        // Try to retrieve the next message.  Return if found.
        final long now = System.currentTimeMillis();
        Message prevMsg = null;
        Message msg = mMessages;
        if (msg != null && msg.target == null) {
          // Stalled by a barrier.  Find the next asynchronous message in the queue.
          do {
            prevMsg = msg;
            msg = msg.next;
          } while (msg != null && !msg.isAsynchronous());
        }
        if (msg != null) {
          if (now < msg.when) {
            // Next message is not ready.  Set a timeout to wake up when it is ready.
            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
          } else {
            // Got a message.
            mBlocked = false;
            if (prevMsg != null) {
              prevMsg.next = msg.next;
            } else {
              mMessages = msg.next;
            }
            msg.next = null;
            if (false) {
              log.info("MessageQueue", "Returning message: " + msg);
            }
            msg.markInUse();
            return msg;
          }
        } else {
          // No more messages.
          nextPollTimeoutMillis = -1;
        }

        // If first time idle, then get the number of idlers to run.
        // Idle handles only run if the queue is empty or if the first message
        // in the queue (possibly a barrier) is due to be handled in the future.
        if (pendingIdleHandlerCount < 0
            && (mMessages == null || now < mMessages.when)) {
          pendingIdleHandlerCount = mIdleHandlers.size();
        }
        if (pendingIdleHandlerCount <= 0) {
          // No idle handlers to run.  Loop and wait some more.
          mBlocked = true;
          continue;
        }

        if (mPendingIdleHandlers == null) {
          mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
        }
        mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
      }

      // Run the idle handlers.
      // We only ever reach this code block during the first iteration.
      for (int i = 0; i < pendingIdleHandlerCount; i++) {
        final IdleHandler idler = mPendingIdleHandlers[i];
        mPendingIdleHandlers[i] = null; // release the reference to the handler

        boolean keep = false;
        try {
          keep = idler.queueIdle();
        } catch (Throwable t) {
          log.error("MessageQueue", "IdleHandler threw exception", t);
        }

        if (!keep) {
          synchronized (this) {
            mIdleHandlers.remove(idler);
          }
        }
      }

      // Reset the idle handler count to 0 so we do not run them again.
      pendingIdleHandlerCount = 0;

      // While calling an idle handler, a new message could have been delivered
      // so go back and look again for a pending message without waiting.
      nextPollTimeoutMillis = 0;
    }
  }

  final void quit() {
    if (!mQuitAllowed) {
      throw new RuntimeException("Main thread not allowed to quit.");
    }

    synchronized (this) {
      if (mQuiting) {
        return;
      }
      mQuiting = true;
    }
    // todo 更改
    // nativeWake(mPtr);
  }

  final int enqueueSyncBarrier(long when) {
    // Enqueue a new sync barrier token.
    // We don't need to wake the queue because the purpose of a barrier is to stall it.
    synchronized (this) {
      final int token = mNextBarrierToken++;
      final Message msg = Message.obtain();
      msg.arg1 = token;

      Message prev = null;
      Message p = mMessages;
      if (when != 0) {
        while (p != null && p.when <= when) {
          prev = p;
          p = p.next;
        }
      }
      if (prev != null) { // invariant: p == prev.next
        msg.next = p;
        prev.next = msg;
      } else {
        msg.next = p;
        mMessages = msg;
      }
      return token;
    }
  }

  final void removeSyncBarrier(int token) {
    // Remove a sync barrier token from the queue.
    // If the queue is no longer stalled by a barrier then wake it.
    final boolean needWake;
    synchronized (this) {
      Message prev = null;
      Message p = mMessages;
      while (p != null && (p.target != null || p.arg1 != token)) {
        prev = p;
        p = p.next;
      }
      if (p == null) {
        throw new IllegalStateException("The specified message queue synchronization "
            + " barrier token has not been posted or has already been removed.");
      }
      if (prev != null) {
        prev.next = p.next;
        needWake = false;
      } else {
        mMessages = p.next;
        needWake = mMessages == null || mMessages.target != null;
      }
      p.recycle();
    }
    if (needWake) {
      // 更改
      // nativeWake(mPtr);
    }
  }

  final boolean enqueueMessage(Message msg, long when) {
    if (msg.isInUse()) {
      throw new RuntimeException(msg + " This message is already in use.");
    }
    if (msg.target == null) {
      throw new RuntimeException("Message must have a target.");
    }

    boolean needWake;
    synchronized (this) {
      if (mQuiting) {
        RuntimeException e = new RuntimeException(
            msg.target + " sending message to a Handler on a dead thread");
        log.info("MessageQueue", e.getMessage(), e);
        return false;
      }

      msg.when = when;
      Message p = mMessages;
      if (p == null || when == 0 || when < p.when) {
        // New head, wake up the event queue if blocked.
        msg.next = p;
        mMessages = msg;
        needWake = mBlocked;
      } else {
        // Inserted within the middle of the queue.  Usually we don't have to wake
        // up the event queue unless there is a barrier at the head of the queue
        // and the message is the earliest asynchronous message in the queue.
        needWake = mBlocked && p.target == null && msg.isAsynchronous();
        Message prev;
        for (; ; ) {
          prev = p;
          p = p.next;
          if (p == null || when < p.when) {
            break;
          }
          if (needWake && p.isAsynchronous()) {
            needWake = false;
          }
        }
        msg.next = p; // invariant: p == prev.next
        prev.next = msg;
      }
    }
    if (needWake) {
      // todo 待更改
      // nativeWake(mPtr);
    }
    return true;
  }

  final boolean hasMessages(Handler h, int what, Object object) {
    if (h == null) {
      return false;
    }

    synchronized (this) {
      Message p = mMessages;
      while (p != null) {
        if (p.target == h && p.what == what && (object == null || p.obj == object)) {
          return true;
        }
        p = p.next;
      }
      return false;
    }
  }

  final boolean hasMessages(Handler h, Runnable r, Object object) {
    if (h == null) {
      return false;
    }

    synchronized (this) {
      Message p = mMessages;
      while (p != null) {
        if (p.target == h && p.callback == r && (object == null || p.obj == object)) {
          return true;
        }
        p = p.next;
      }
      return false;
    }
  }

  final void removeMessages(Handler h, int what, Object object) {
    if (h == null) {
      return;
    }

    synchronized (this) {
      Message p = mMessages;

      // Remove all messages at front.
      while (p != null && p.target == h && p.what == what
          && (object == null || p.obj == object)) {
        Message n = p.next;
        mMessages = n;
        p.recycle();
        p = n;
      }

      // Remove all messages after front.
      while (p != null) {
        Message n = p.next;
        if (n != null) {
          if (n.target == h && n.what == what
              && (object == null || n.obj == object)) {
            Message nn = n.next;
            n.recycle();
            p.next = nn;
            continue;
          }
        }
        p = n;
      }
    }
  }

  final void removeMessages(Handler h, Runnable r, Object object) {
    if (h == null || r == null) {
      return;
    }

    synchronized (this) {
      Message p = mMessages;

      // Remove all messages at front.
      while (p != null && p.target == h && p.callback == r
          && (object == null || p.obj == object)) {
        Message n = p.next;
        mMessages = n;
        p.recycle();
        p = n;
      }

      // Remove all messages after front.
      while (p != null) {
        Message n = p.next;
        if (n != null) {
          if (n.target == h && n.callback == r
              && (object == null || n.obj == object)) {
            Message nn = n.next;
            n.recycle();
            p.next = nn;
            continue;
          }
        }
        p = n;
      }
    }
  }

  final void removeCallbacksAndMessages(Handler h, Object object) {
    if (h == null) {
      return;
    }

    synchronized (this) {
      Message p = mMessages;

      // Remove all messages at front.
      while (p != null && p.target == h
          && (object == null || p.obj == object)) {
        Message n = p.next;
        mMessages = n;
        p.recycle();
        p = n;
      }

      // Remove all messages after front.
      while (p != null) {
        Message n = p.next;
        if (n != null) {
          if (n.target == h && (object == null || n.obj == object)) {
            Message nn = n.next;
            n.recycle();
            p.next = nn;
            continue;
          }
        }
        p = n;
      }
    }
  }
}
